<?php
declare (strict_types = 1);

namespace app\command;

use app\admin\controller\Qbapi;
use app\admin\controller\Trapi;
use think\admin\model\SystemDownload;
use think\admin\model\SystemSites;
use think\console\Input;
use think\console\Output;
use think\facade\Log;

use app\exception\KnownException;
use app\helper\CacheHelper;
use app\helper\FastRequest;
use app\helper\TorrentHelper;

class Cron extends \think\admin\Command

{
    protected function configure()
    {
        // 指令配置
        $this->setName('cron')
             ->setDescription('辅种计划任务');
    }

    protected function execute(Input $input, Output $output)
    {
        // 获取所有参与辅种的下载器ID
        $downloadIds = $this->queue->data;

        $statistics = [];
        foreach ($downloadIds as $downloadId) {
            if(!$downloadId){
                $msg = '请检查该任务的下载器配置';
                $statistics[] = [
                    'name'    =>  '未知',
                    'error'   =>  $msg
                ];
                $this->setQueueProgress($msg, '100');
                continue;
            }

            // 查询当前下载器ID的数据库信息
            $downloadResult = SystemDownload::mQuery()->db()->where('id', $downloadId)->find();
            if (!$downloadResult) {
                $msg = 'id：' . $downloadId . '下载器不存在或已删除，请重新编辑保存该定时任务';
                $statistics[$downloadId] = [
                    'name'    =>  '未知',
                    'error'   =>  $msg
                ];
                $this->setQueueProgress($msg, '100');
                continue;
            }

            $msg = '正在执行' . $downloadResult['name'] . '下载器的辅种任务';
            $this->setQueueProgress($msg, '10');

            // 查询当前下载器配置的参与辅种的站点信息
            $siteResult = SystemSites::mQuery()->db()->whereIn('id', array_map('intval', explode(',', $downloadResult['reseed_sites'])))->select();
            if (count($siteResult) == 0) {
                $msg = '下载器的辅种站点为空';
                $statistics[$downloadId] = [
                    'name'    =>  $downloadResult['name'],
                    'error'   =>  $msg
                ];
                $this->setQueueProgress($msg, '100');
                continue;
            }

            $msg = '正在检索种子目录...';
            $this->setQueueProgress($msg, '10');

            $torrent_list = TorrentHelper::get_by_path($downloadResult['save_path'], $downloadResult['type']);
            $pieces_hash_info = $torrent_list['pieces_hash_info'];
            $tracker_pieces = $torrent_list['tracker_pieces'];
            $info_hash_pieces = $torrent_list['info_hash_pieces'];

            if(count($info_hash_pieces) == 0){
                $msg = '目录中检索到种子数为0，请检查映射目录是否正确';
                $statistics[$downloadId] = [
                    'name'    =>  $downloadResult['name'],
                    'error'   =>  $msg
                ];
                $this->setQueueProgress($msg, '100');
                continue;
            }
            $msg = '正在检索下载器中有效种子数...';
            $this->setQueueProgress($msg, '30');

            $info_list = [];
            if ($downloadResult['type'] == 1) {
                $qbapi = new Qbapi($downloadResult);
                $info_list = TorrentHelper::qb_getTorrentsInfo($qbapi, $info_hash_pieces);
            }else{
                $trapi = new Trapi($downloadResult);
                $info_list = TorrentHelper::tr_getTorrentsInfo($trapi, $info_hash_pieces);
            }

            if(count($info_list) == 0){
                $msg = '下载器中检索到有效种子数为0，请检查下载器是否有[已完成]的种子或检查种子目录配置是否一致';
                $statistics[$downloadId] = [
                    'name'    =>  $downloadResult['name'],
                    'error'   =>  $msg
                ];
                $this->setQueueProgress($msg, '100');
                continue;
            }

            /**
             * 过滤出有效的pieces
             * 如果文件夹中有，但找不到有效的，可能原因：
             * 1、文件映射错了（qb->tr,tr->qb）
             * 2、配置了qb的[复制.torrent文件到]&在下载器中删除了种子，但是不会删除复制了的种子
             * 3、种子未完成
             */
            $pieces_hash_info = TorrentHelper::get_valid_pieces_hash_info($pieces_hash_info, $info_list);
            
            $msg = '种子数：' . count($info_hash_pieces) . '|有效种子数：' . count($info_list) . '|去重后种子数：' . count($pieces_hash_info);
            $this->setQueueProgress($msg, '40');
            $statistics[$downloadId] = [
                'info_count'          =>   count($info_hash_pieces),
                'info_valid_count'    =>   count($info_list),
                'pieces_valid_count'  =>   count($pieces_hash_info),
                'name'                =>   $downloadResult['name'],  
                'sites'               =>   []
            ];

            $progressIndex = 40;
            // 先查参与辅种站点的缓存
            $cached_list = [];
            foreach ($siteResult as $site) {
                $cached_key = $this->cached_key($site);
                $actived_key = $this->actived_key($site);

                // 获取已缓存&该站已辅种过的
                $cached_pieces_hash_list = CacheHelper::GetPiecesHash($site['site_url']);
                $exists_pieces_hash_list = TorrentHelper::get_pieces_by_site($site, $tracker_pieces);

                // 过滤掉已缓存的&该站已辅种过的
                $cached_list[$actived_key] = array_diff(array_diff(array_keys($pieces_hash_info), $cached_pieces_hash_list), $exists_pieces_hash_list);

                $progressIndex++;
                $msg = $site['site_name'] . '|已存在：' . count($exists_pieces_hash_list) . '|已缓存：'.count($cached_pieces_hash_list) . '|待辅种: ' . count($cached_list[$actived_key]);
                $this->setQueueProgress($msg, (string)($progressIndex <= 60 ? $progressIndex : 60));

                // 将已辅过的加入缓存，放在这个位置是为了上面的提示中[已缓存]数量正常显示
                $cached_list[$cached_key] = array_unique(array_merge($cached_pieces_hash_list, $exists_pieces_hash_list));
                CacheHelper::SetPiecesHash($site['site_url'], $cached_list[$cached_key]);
            
                $statistics[$downloadId]['sites'][$site['site_url']] = [
                    'exists'                    =>  count($exists_pieces_hash_list),
                    'cached'                    =>  count($cached_pieces_hash_list),
                    'actived'                   =>  count($cached_list[$actived_key]),
                    'cached_begin_count'        =>  count($cached_list[$cached_key]),
                    'name'                      =>  $site['site_name'],  
                    'notexist_count'            =>  0,
                    'succeeded_count'           =>  0,
                    'download_failed_count'     =>  0,
                ];
            }

            foreach ($siteResult as $site) {
                try{
                    if (!isset($site['passkey']) || $site['passkey'] == ''){
                        $msg = $site['site_name'] . '未配置passkey，跳过辅种';
                        $statistics[$downloadId]['sites'][$site['site_url']]['error'] = $msg;
                        $this->setQueueProgress($msg, '70');
                        continue;
                    }

                    $cached_key = $this->cached_key($site);
                    $actived_key = $this->actived_key($site);
                    $cached_pieces_hash_list = $cached_list[$cached_key];
                    $actived_pieces_hash_list = $cached_list[$actived_key];
    
                    if(count($actived_pieces_hash_list) == 0){
                        $msg = $site['site_name'] . '待辅种种子数为空，跳过辅种';
                        $statistics[$downloadId]['sites'][$site['site_url']]['error'] = $msg;
                        $this->setQueueProgress($msg, '70');
                        continue;
                    }
    
                    $msg = '正在请求' . $site['site_name'] . "站点";
                    $this->setQueueProgress($msg, '70');

                    // 每次请求间隔
                    $request_interval = $this->request_interval($site);
                    $first_request = true;
                    $pieces_hash_groups = array_chunk($actived_pieces_hash_list, 100);
                    foreach ($pieces_hash_groups as $group_list) {
    
                        if(!$first_request){
                            sleep($request_interval);
                        }
                        $first_request = false;
    
                        $download_url = [];
                        try{
                            $download_url = FastRequest::pieces_request($site, $group_list);
                        }catch (KnownException $e) {
                            $msg = $e->getMessage();
                            $statistics[$downloadId]['sites'][$site['site_url']]['error'] = $msg;
                            $this->setQueueProgress($msg, '70');
                            continue;
                        }

                        $msg = '本次请求返回' . count($download_url) . "个种子，下载中...";
                        $this->setQueueProgress($msg, '70');

                        if ($downloadResult['type'] == 1) {
                            $this->qb($downloadResult, $site, $download_url, $pieces_hash_info, $cached_pieces_hash_list, $first_request, $statistics, $group_list);
                        } else {
                            $this->tr($downloadResult, $site, $download_url, $pieces_hash_info, $cached_pieces_hash_list, $first_request, $statistics, $group_list);
                        }
                    }
                }catch(\Exception $e){
                    $msg = $site['site_name'] . '异常中断，' . $e->getMessage();
                    $statistics[$downloadId]['sites'][$site['site_url']]['error'] = $msg;
                    $this->setQueueProgress($msg, '70');
                    continue;
                }
            }
        }
        
        $this->statistics_output($statistics);
    }

    /**
     * qb
     * @param mixed $downloadResult             下载器信息
     * @param mixed $site                       站点信息
     * @param array $download_url               种子下载地址 参考FastRequest::pieces_request返回值
     * @param array $pieces_hash_info           种子信息 参考TorrentHelper::get_valid_pieces_hash_info返回值
     * @param array $cached_pieces_hash_list    该站辅种成功的缓存[引用]
     * @param bool  $first_request              是否是首次请求该站[引用]
     * @param array $statistics                 统计[引用]
     * @param array $request_pieces_hash_info   本次请求的种子信息
     * @return void
     */
    private function qb(
        $downloadResult, 
        $site, 
        array $download_url, 
        array $pieces_hash_info, 
        array &$cached_pieces_hash_list, 
        bool &$first_request,
        array &$statistics,
        array $request_pieces_hash_info) {
        $qbapi = new Qbapi($downloadResult);

        // 每次请求间隔
        $request_interval = $this->request_interval($site);
        $statistics[$downloadResult['id']]['sites'][$site['site_url']]['notexist_count'] += (count($request_pieces_hash_info) - count($download_url));

        foreach ($download_url as $key => $value) {
            // 正常不会，除非站点返回错了
            if(!isset($pieces_hash_info[$key])){
                continue;
            }

            $save_path = $pieces_hash_info[$key][0]['save_path'];
            $msg = '保存目录：' . $save_path . "下载站点：" . $site['site_url'];
            $this->setQueueProgress($msg, '70');

            if(!$first_request){
                sleep($request_interval);
            }
            $first_request = false;
            
            if ($qbapi->addTorrent(
                $value,
                $save_path,
                $downloadResult['is_skip_hash'],
                $downloadResult['is_paused'],
                $site['download_limit'],
                $site['upload_limit'],
                'Reseed Puppy') == 'Ok.') {
                
                array_push($cached_pieces_hash_list, $key);
                CacheHelper::SetPiecesHash($site['site_url'], $cached_pieces_hash_list);

                $statistics[$downloadResult['id']]['sites'][$site['site_url']]['succeeded_count'] += 1;
            }else{
                // TODO:移除种子库
                $statistics[$downloadResult['id']]['sites'][$site['site_url']]['download_failed_count'] += 1;
            }
        }
    }

    /**
     * tr
     * @param mixed $downloadResult             下载器信息
     * @param mixed $site                       站点信息
     * @param array $download_url               种子下载地址 参考FastRequest::pieces_request返回值
     * @param array $pieces_hash_info           种子信息 参考TorrentHelper::get_valid_pieces_hash_info返回值
     * @param array $cached_pieces_hash_list    该站辅种成功的缓存[引用]
     * @param bool  $first_request              是否是首次请求该站[引用]
     * @param array $statistics                 统计[引用]
     * @param array $request_pieces_hash_info   本次请求的种子信息
     * @return void
     */
    private function tr(
        $downloadResult, 
        $site, 
        array $download_url, 
        array $pieces_hash_info,
        array &$cached_pieces_hash_list, 
        bool &$first_request,
        array &$statistics,
        array $request_pieces_hash_info) {
        $trapi = new Trapi($downloadResult);

        // 每次请求间隔
        $request_interval = $this->request_interval($site);
        $statistics[$downloadResult['id']]['sites'][$site['site_url']]['notexist_count'] += (count($request_pieces_hash_info) - count($download_url));

        foreach ($download_url as $key => $value) {
            // 正常不会，除非站点返回错了
            if(!isset($pieces_hash_info[$key])){
                continue;
            }

            $save_path = $pieces_hash_info[$key][0]['save_path'];
            $msg = '保存目录：' . $save_path . "下载站点：" . $site['site_url'];
            $this->setQueueProgress($msg, '70');
            
            if(!$first_request){
                sleep($request_interval);
            }
            $first_request = false;

            $result = $trapi->addTorrent(
                $value,
                $save_path,
                $downloadResult['is_paused']);
            // 下载器已存在的也是成功，但$result['arguments']['torrent-duplicate']有值
            if ($result['result'] == 'success') {

                array_push($cached_pieces_hash_list, $key);
                CacheHelper::SetPiecesHash($site['site_url'], $cached_pieces_hash_list);

                $statistics[$downloadResult['id']]['sites'][$site['site_url']]['succeeded_count'] += 1;
            }else{
                // TODO:移除种子库
                $statistics[$downloadResult['id']]['sites'][$site['site_url']]['download_failed_count'] += 1;
            }
        }
    }

    /**
     * 已缓存键
     * @param mixed $site
     * @return string
     */
    private function cached_key($site) : string {
        return $site['site_url'] . 'cached';
    }

    /**
     * 待辅种键
     * @param mixed $site
     * @return string
     */
    private function actived_key($site) : string {
        return $site['site_url'] . 'actived';
    }

    /**
     * 每次对站点的请求间隔
     * @param mixed $site
     * @return integer
     */
    private function request_interval($site) : int {
        return intval($site['request_interval']) > 2  ? intval($site['request_interval']) : 2;
    }

    /**
     * 统计输出
     * @param array $statistics
     * [
     *     '$downloadId'    =>   [
     * 	        'info_count'          =>   '种子',
	 * 	        'info_valid_count'    =>   '有效种子',
	 * 	        'pieces_valid_count'  =>   '去重后种子数',
	 * 	        'name'                =>   '下载器名称',  
	 * 	        'error'               =>   '错误信息',
	 * 	        'sites'               =>   [
	 * 	            'site_url'        =>   [
	 * 	                'exists'                =>  count($exists_pieces_hash_list),
	 * 	                'cached'                =>  count($cached_pieces_hash_list),
	 * 	                'actived'               =>  count($cached_list[$actived_key]),
	 * 	                'cached_begin_count'    =>  count($cached_list[$cached_key]),
	 * 	                'name'                  =>  '站点名',  
	 * 	                'notexist_count'        =>  '不存在种子',
	 * 	                'succeeded_count'       =>  '成功',
	 * 	                'download_failed_count' =>  '下载器下载种子失败',
     *              ]
     *          ]
     *     ]
     * ]
     * @return void
     */
    private function statistics_output($statistics){
        $error = null;
        $this->setQueueProgress('———————————辅种完成统计———————————', '99');

        try{
            foreach($statistics as $downloadId => $download){
                if(isset($download['error'])){
                    $msg = '[✖][' . $download['name'] . ']下载器发生异常->' . $download['error'];
                    $this->setQueueProgress($msg, '99');
                    $error = $msg;
                }else{
                    $msg = '[✔][' . $download['name'] . ']下载器->种子数：' . $download['info_count'] . '|有效种子数：' . $download['info_valid_count']  . '|去重后种子数：' . $download['pieces_valid_count'];
                    $this->setQueueProgress($msg, '99');
                }
    
                if(!isset($download['sites'])){
                    continue;
                }

                $sites = $download['sites'];
                foreach($sites as $site_url => $site){
                    $incomplete = $site['actived'] - $site['succeeded_count'] - $site['notexist_count'] - $site['download_failed_count'];
                    $msg = '[' . $site['name'] . ']未完成：' . $incomplete . '|参与辅种：' . $site['actived'] . '|成功：' . $site['succeeded_count'] . '|跳过（不存在）：' . $site['notexist_count'];
                    if($site['download_failed_count'] > 0){
                        $msg .= '|失败: ' . $site['download_failed_count'];
                    }
                    
                    if(isset($site['error'])){
                        $msg = '[✖]' . $msg;
                        $msg .= '|错误->'.$site['error'];
                        $error = $site['error'];
                    }else{
                        $msg = '[✔]' . $msg;
                    }

                    $this->setQueueProgress($msg, '99');
                }
                $this->setQueueProgress('————————————————————————', '99');
            }
        }
        catch(\Exception $e){
            Log::error($msg . $e->getMessage());
        }

        $this->setQueueProgress('———————————辅种完成统计———————————', '100');

        if(empty($error)){
            $this->setQueueSuccess('任务执行完成');
        }else{
            // 此条会输出到exec_desc字段中
            $this->setQueueError('最新错误信息->' . $error . '|更多信息查看日志');
        }
    }
}
